package korkd.tower;

import org.anddev.andengine.entity.IEntity;
import org.anddev.andengine.entity.modifier.EntityModifier;
import org.anddev.andengine.entity.modifier.MoveModifier;
import org.anddev.andengine.util.modifier.IModifier;
import org.anddev.andengine.util.modifier.SequenceModifier;
import org.anddev.andengine.util.modifier.SequenceModifier.ISubSequenceModifierListener;
import org.anddev.andengine.util.modifier.ease.IEaseFunction;
import org.anddev.andengine.util.path.Path;

import android.util.FloatMath;

public class AStarPathModifier extends EntityModifier {
	// ===========================================================
	// Constants
	// ===========================================================

	// ===========================================================
	// Fields
	// ===========================================================

	private final SequenceModifier<IEntity> mSequenceModifier;

	private IAStarPathModifierListener mPathModifierListener;

	private final Path mPath;

	private int[] mTileDimensions;

	// ===========================================================
	// Constructors
	// ===========================================================

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions) {
		this(pDuration, pPath, pTileDimensions, null, null, IEaseFunction.DEFAULT);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IEaseFunction pEaseFunction) {
		this(pDuration, pPath, pTileDimensions, null, null, pEaseFunction);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IEntityModifierListener pEntityModiferListener) {
		this(pDuration, pPath, pTileDimensions, pEntityModiferListener, null, IEaseFunction.DEFAULT);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IAStarPathModifierListener pPathModifierListener) {
		this(pDuration, pPath, pTileDimensions, null, pPathModifierListener, IEaseFunction.DEFAULT);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IAStarPathModifierListener pPathModifierListener, final IEaseFunction pEaseFunction) {
		this(pDuration, pPath, pTileDimensions, null, pPathModifierListener, pEaseFunction);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IEntityModifierListener pEntityModiferListener, final IEaseFunction pEaseFunction) {
		this(pDuration, pPath, pTileDimensions, pEntityModiferListener, null, pEaseFunction);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IEntityModifierListener pEntityModiferListener, final IAStarPathModifierListener pPathModifierListener) throws IllegalArgumentException {
		this(pDuration, pPath, pTileDimensions, pEntityModiferListener, pPathModifierListener, IEaseFunction.DEFAULT);
	}

	public AStarPathModifier(final float pDuration, final Path pPath, final int[] pTileDimensions, final IEntityModifierListener pEntityModiferListener, final IAStarPathModifierListener pPathModifierListener, final IEaseFunction pEaseFunction) throws IllegalArgumentException {
		super(pEntityModiferListener);
		final int pathSize = pPath.getLength();

		if (pathSize < 2) {
			throw new IllegalArgumentException("Path needs at least 2 waypoints!");
		}
		
		int pathLength = 0;
		
		// Get the length of the path in coordinates
		for(int i=1; i < pathSize; i++)
		{
			if(pPath.getTileColumn(i) > pPath.getTileColumn(i-1))
			{
				pathLength += pTileDimensions[0];
			}
			else
			{
				pathLength += pTileDimensions[1];
			}
		}

		this.mTileDimensions = pTileDimensions;
		this.mPath = pPath;
		this.mPathModifierListener = pPathModifierListener;

		final MoveModifier[] moveModifiers = new MoveModifier[pathSize - 1];

		final float velocity = pathLength / pDuration;

		final int modifierCount = moveModifiers.length;
		for(int i = 0; i < modifierCount; i++) {
			final float duration = getSegmentLength(i) / velocity;
			moveModifiers[i] = new MoveModifier(duration, getXCoordinates(i), getXCoordinates(i + 1), getYCoordinates(i), getYCoordinates(i + 1), null, pEaseFunction);
		}

		/* Create a new SequenceModifier and register the listeners that
		 * call through to mEntityModifierListener and mPathModifierListener. */
		this.mSequenceModifier = new SequenceModifier<IEntity>(
				new ISubSequenceModifierListener<IEntity>() {
					@Override
					public void onSubSequenceStarted(final IModifier<IEntity> pModifier, final IEntity pEntity, final int pIndex) {
						if(pIndex < pathSize)
		                {
		            		switch(pPath.getDirectionToNextStep(pIndex)) {
		                            case DOWN:
		                            	if(AStarPathModifier.this.mPathModifierListener != null) {
		        							AStarPathModifier.this.mPathModifierListener.onNextMoveDown(AStarPathModifier.this, pEntity, pIndex);
		        						}
		                                break;
		                            case RIGHT:
		                            	if(AStarPathModifier.this.mPathModifierListener != null) {
		        							AStarPathModifier.this.mPathModifierListener.onNextMoveRight(AStarPathModifier.this, pEntity, pIndex);
		        						}
	                                    break;
		                            case UP:
		                            	if(AStarPathModifier.this.mPathModifierListener != null) {
		        							AStarPathModifier.this.mPathModifierListener.onNextMoveUp(AStarPathModifier.this, pEntity, pIndex);
		        						}
	                                    break;
		                            case LEFT:
		                            	if(AStarPathModifier.this.mPathModifierListener != null) {
		        							AStarPathModifier.this.mPathModifierListener.onNextMoveLeft(AStarPathModifier.this, pEntity, pIndex);
		        						}
	                                	break;
		                        	default:
		                        			
		                    }
		                }
						
						if(AStarPathModifier.this.mPathModifierListener != null) {
							AStarPathModifier.this.mPathModifierListener.onPathWaypointStarted(AStarPathModifier.this, pEntity, pIndex);
						}
					}

					@Override
					public void onSubSequenceFinished(final IModifier<IEntity> pEntityModifier, final IEntity pEntity, final int pIndex) {
						if(AStarPathModifier.this.mPathModifierListener != null) {
							AStarPathModifier.this.mPathModifierListener.onPathWaypointFinished(AStarPathModifier.this, pEntity, pIndex);
						}
					}
				},
				new IEntityModifierListener() {
					@Override
					public void onModifierStarted(final IModifier<IEntity> pModifier, final IEntity pEntity) {
						AStarPathModifier.this.onModifierStarted(pEntity);
						if(AStarPathModifier.this.mPathModifierListener != null) {
							AStarPathModifier.this.mPathModifierListener.onPathStarted(AStarPathModifier.this, pEntity);
						}
					}

					@Override
					public void onModifierFinished(final IModifier<IEntity> pEntityModifier, final IEntity pEntity) {
						AStarPathModifier.this.onModifierFinished(pEntity);
						if(AStarPathModifier.this.mPathModifierListener != null) {
							AStarPathModifier.this.mPathModifierListener.onPathFinished(AStarPathModifier.this, pEntity);
						}
					}
				},
				moveModifiers
		);
	}

	private float getXCoordinates(int pIndex) {
		return (mPath.getStep(pIndex).getTileColumn() * mTileDimensions[0]) + 4;
	}

	private float getYCoordinates(int pIndex) {
		return (mPath.getStep(pIndex).getTileRow() * mTileDimensions[1]) + 4;
	}
	
	private float getSegmentLength(int pIndex) {

        final int nextSegmentIndex = pIndex + 1;

        final float dx = getXCoordinates(pIndex) - getXCoordinates(nextSegmentIndex);
        final float dy = getYCoordinates(pIndex) - getYCoordinates(nextSegmentIndex);

        return FloatMath.sqrt(dx * dx + dy * dy);
	}

	protected AStarPathModifier(final AStarPathModifier pPathModifier) throws DeepCopyNotSupportedException  {
		this.mPath = new Path();
		for(int i=0;i<pPathModifier.getPath().getLength();i++)
		{
			this.mPath.append(pPathModifier.getPath().getStep(i));
		}
		this.mSequenceModifier = pPathModifier.mSequenceModifier.deepCopy();
	}
	
	@Override
	public AStarPathModifier deepCopy() throws DeepCopyNotSupportedException {
		return new AStarPathModifier(this);
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	public Path getPath() {
		return this.mPath;
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public boolean isFinished() {
		return this.mSequenceModifier.isFinished();
	}

	@Override
	public float getSecondsElapsed() {
		return this.mSequenceModifier.getSecondsElapsed();
	}

	@Override
	public float getDuration() {
		return this.mSequenceModifier.getDuration();
	}

	public IAStarPathModifierListener getPathModifierListener() {
		return this.mPathModifierListener;
	}

	public void setPathModifierListener(final IAStarPathModifierListener pPathModifierListener) {
		this.mPathModifierListener = pPathModifierListener;
	}

	@Override
	public void reset() {
		this.mSequenceModifier.reset();
	}

	@Override
	public float onUpdate(final float pSecondsElapsed, final IEntity pEntity) {
		return this.mSequenceModifier.onUpdate(pSecondsElapsed, pEntity);
	}

	// ===========================================================
	// Methods
	// ===========================================================

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================

	public static interface IAStarPathModifierListener {
		// ===========================================================
		// Constants
		// ===========================================================

		// ===========================================================
		// Fields
		// ===========================================================

		public void onPathStarted(final AStarPathModifier pPathModifier, final IEntity pEntity);
		public void onNextMoveLeft(AStarPathModifier aStarPathModifier, IEntity pEntity, int pIndex);
		public void onNextMoveUp(AStarPathModifier aStarPathModifier, IEntity pEntity, int pIndex);
		public void onNextMoveRight(AStarPathModifier aStarPathModifier, IEntity pEntity, int pIndex);
		public void onNextMoveDown(AStarPathModifier aStarPathModifier, IEntity pEntity, int pIndex);
		public void onPathWaypointStarted(final AStarPathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex);
		public void onPathWaypointFinished(final AStarPathModifier pPathModifier, final IEntity pEntity, final int pWaypointIndex);
		public void onPathFinished(final AStarPathModifier pPathModifier, final IEntity pEntity);
	}
}
